package com.youlu.campus.admin.auth.shiro.realm;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import com.mongodb.client.result.UpdateResult;
import com.youlu.campus.admin.auth.entity.SystemResource;
import com.youlu.campus.admin.auth.entity.SystemUser;
import com.youlu.campus.admin.auth.entity.exception.AuthErrorCode;
import com.youlu.campus.admin.auth.service.PasswordService;
import com.youlu.campus.admin.auth.service.ResourceService;
import com.youlu.campus.admin.auth.service.RoleService;
import com.youlu.campus.admin.auth.service.UserService;
import com.youlu.campus.admin.auth.shiro.credentials.AuthCredentialsService;
import com.youlu.campus.admin.auth.shiro.exception.AuthException;
import com.youlu.campus.admin.auth.shiro.token.AuthPrincipal;
import com.youlu.campus.admin.auth.shiro.token.AuthToken;
import com.youlu.campus.admin.auth.shiro.token.AuthType;
import com.youlu.campus.common.exception.BusinessException;

import com.youlu.campus.common.utils.SM2CryptUtils;
import com.youlu.campus.entity.WechatAuthBlackList;
import com.youlu.campus.service.wechat.WechatAuthBlackListService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Created by zhuhuaiqi
 *
 * @author zhuhuaiqi
 */
public class AuthRealm extends AuthorizingRealm {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    PasswordService passwordService;

    @Autowired
    UserService userService;

    @Autowired
    ResourceService resourceService;

    @Autowired
    private RoleService roleService;

    private AuthCredentialsService authCredentialsService;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private WechatAuthBlackListService wechatAuthBlackListService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof AuthToken;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
            AuthenticationException {
//        logger.info("AuthRealm.doGetAuthenticationInfo token：{}", JSON.toJSONString(token));
        AuthToken authToken = (AuthToken) token;

        AuthType authType = authToken.getAuthType();

        SimpleAuthenticationInfo info = null;
        if (Objects.isNull(authType)) {
            authType = AuthType.USERNAME_PASSWORD;
        }
        switch (authType) {
            case USERNAME_PASSWORD:
                info = authUsernamePassword(authToken);
                break;
            case ACCESS_CREDENTIALS:
                info = authAccessCredentials(authToken);
                break;
            default:
                throw new IncorrectCredentialsException("unknown auth type");
        }
        return info;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.debug("AuthRealm.doGetAuthorizationInfo principals:{}, principals instance:{}", JSON.toJSONString(principals), principals);

        AuthPrincipal authPrincipal = (AuthPrincipal) principals.getPrimaryPrincipal();

//        logger.debug("AuthRealm.doGetAuthorizationInfo authPrincipal:{}", JSON.toJSONString(authPrincipal));

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        List<SystemResource> resources = resourceService.listByComIdAndUserId(authPrincipal.getUid(),
                authPrincipal.getComId());

        if (CollectionUtils.isEmpty(resources)) {
            return authorizationInfo;
        }
        List<String> roleIdList = roleService.getTenantRoleIds(authPrincipal.getComId());
        //权限名的集合
        Set<String> permissions = Sets.newHashSet();
        for (SystemResource resource : resources) {
            if (StringUtils.isNotEmpty(resource.getPermission())) {
                permissions.add(resource.getPermission());
            }
            if (StringUtils.isNotEmpty(resource.getUrl()) && resource.getType() != 3) { //非菜单不纳入
                authorizationInfo.addStringPermission(resource.getUrl());
            }
        }
        authorizationInfo.addStringPermissions(permissions);
        //增加角色
        if (CollectionUtils.isNotEmpty(roleIdList)) {
            authorizationInfo.setRoles(roleIdList.stream().collect(Collectors.toSet()));
        }
        logger.debug("doGetAuthorizationInfo permissions :{}", JSON.toJSONString(authorizationInfo.getStringPermissions()));

        return authorizationInfo;
    }

    protected SimpleAuthenticationInfo authUsernamePassword(AuthToken authToken) {
        logger.info("开始获取用户鉴权信息:{}", JSON.toJSONString(authToken));
        SystemUser user = null;
        try {
            user = userService.get(authToken.getComId(), authToken.getUsername(), authToken.getDomain());
        } catch (BusinessException be) {
            logger.error("获取用户信息错误:{}", be);
            throw new AuthException(AuthErrorCode.INVALID_ARGUMENT);
        }
        if (Boolean.TRUE.equals(user.getForbidden())) {
            logger.error("用户:{} 被封禁,请联系管理员", user.getLoginName());
            throw new AuthException(AuthErrorCode.F_LOGIN);
        }
        this.checkPasswordRetry(user);
        char[] c = (char[]) authToken.getCredentials();
        String str = String.valueOf(c);
        logger.info("authToken.getCredentials():{}", authToken.getCredentials().getClass());
        logger.info("用户密码:{},校验:{}", user.getEncPwd(), str.equalsIgnoreCase(user.getEncPwd()));
        if (!this.validPassword(user, str)) {
            logger.error("用户密码错误");
            updatePasswordRetryNum(user, 1);
            //TODO:设置密码错误验证问题
            throw new AuthException(AuthErrorCode.ERROR_PASSWORD);
        }
        updateUserLoginInfo(user, "");
        authToken.setComId(user.getTenantId());
        authToken.setUid(user.getId());
        authToken.setDomain(user.getDomain());
        ByteSource b = passwordService.getCredentialsSalt(user);
        logger.debug("authUsernamePassword user:{},credentialsSalt:{}", JSON.toJSONString(user), JSON.toJSONString(b));
        return new SimpleAuthenticationInfo(user.getLoginName(), user.getEncPwd(),
                b, getName());
    }

    protected SimpleAuthenticationInfo authAccessCredentials(AuthToken authToken) {
        return new SimpleAuthenticationInfo(authToken.getUsername(), authToken.getCredentials(),
                getName());
    }

    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }


    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }

    public AuthCredentialsService getAuthCredentialsService() {
        return authCredentialsService;
    }

    public void setAuthCredentialsService(AuthCredentialsService authCredentialsService) {
        this.authCredentialsService = authCredentialsService;
    }

    private boolean validPassword(SystemUser user, String password) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // get the request
        HttpServletRequest request1 = requestAttributes.getRequest();
        String sai = request1.getHeader("sai");
        WechatAuthBlackList wechatAuthBlackList = wechatAuthBlackListService.findBySysAppId(sai);
        if (Objects.isNull(wechatAuthBlackList)) {
            logger.error("未配置平台开放信息，请联系管理员");
            throw new com.youlu.campus.base.exception.BusinessException("未配置平台开放信息，请联系管理员");
        }
        String encPwd = SM2CryptUtils.decrypt(user.getEncPwd(), wechatAuthBlackList.getPrivatekey());
        if (password.equals(encPwd)) {
            return true;
        }
        //TODO：调试完删除
        logger.error("密码校验错误:{},{}", password, encPwd);
        return false;
    }

    private void updateUserLoginInfo(SystemUser user, String ip) {
        Query query = new Query().addCriteria(Criteria.where("id").is(user.getId()));
        Update update = new Update();
        Date date = new Date();
        update.set("updatedTime", date);
        update.set("status", 2);
        update.set("lastLoginTime", date);
        UpdateResult result = mongoTemplate.updateFirst(query, update, SystemUser.class);
        logger.info("更新用户:{} 首次登陆和状态结果为:{}", user.getId(), result.getModifiedCount() > 0 ? true : false);


    }

    private void updatePasswordRetryNum(SystemUser user, Integer num) {
        Query query = new Query().addCriteria(Criteria.where("id").is(user.getId()));
        Update update = new Update();
        Date date = new Date();
        update.set("updatedTime", date);
        update.inc("passwordRetryNum", num);
        UpdateResult result = mongoTemplate.updateFirst(query, update, SystemUser.class);
        logger.info("更新用户:{} 密码错误重试次数:{}", user.getId(), result.getModifiedCount() > 0 ? true : false);
    }

    private void checkPasswordRetry(SystemUser user) {
        if (Objects.nonNull(user.getPasswordRetryNum()) && user.getPasswordRetryNum().compareTo(5) == 1) {
            this.updateFerbid(user);
            throw new AuthException(AuthErrorCode.PASSWORD_RETRY);
        }
    }

    private void updateFerbid(SystemUser user) {
        Query query = new Query().addCriteria(Criteria.where("id").is(user.getId()));
        Update update = new Update();
        Date date = new Date();
        update.set("updatedTime", date);
        update.set("forbidden", true);
        UpdateResult result = mongoTemplate.updateFirst(query, update, SystemUser.class);
        logger.info("更新用户:{} 封禁状态:{}", user.getId(), result.getModifiedCount() > 0 ? true : false);
    }

}
